﻿using EtherCAT.NET.Infrastructure;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Serialization;

namespace Jodell.Transport.Ethercat
{
    public class EsiUtilities
    {
        private static List<EtherCATInfo> CacheEtherCatInfos { get; set; } = new List<EtherCATInfo>();

        public static bool IsLoaded { get; private set; }

        public static void LoadEsiCache(string dir)
        {
            IsLoaded = false;
            CacheEtherCatInfos.Clear();
            foreach (var file in Directory.GetFiles(dir, "*.xml"))
            {
                try
                {
                    var info = loadEsi(file);
                    if (info != null)
                    {
                        CacheEtherCatInfos.Add(info);
                    }
                }
                catch { }
            }
            IsLoaded = true;
        }
        public static long ParseHexDecString(string value)
        {
            if (value.StartsWith("#x"))
                return uint.Parse(value.Replace("#x", string.Empty), NumberStyles.HexNumber);
            else
                return long.Parse(value);
        }
        private static (EtherCATInfo etherCATInfo, EtherCATInfoDescriptionsDevice device, EtherCATInfoDescriptionsGroup group) TryFindDevice(List<EtherCATInfo> etherCATInfos, uint manufacturer, uint productCode, uint revision)
        {
            EtherCATInfo info = null;
            EtherCATInfoDescriptionsDevice device = null;

            foreach (var currentInfo in etherCATInfos)
            {
                var vendorId = (uint)EsiUtilities.ParseHexDecString(currentInfo.Vendor.Id);

                if (vendorId != manufacturer)
                    continue;

                device = currentInfo.Descriptions.Devices.FirstOrDefault(currentDevice =>
                {
                    var found = !string.IsNullOrWhiteSpace(currentDevice.Type.ProductCode) &&
                                !string.IsNullOrWhiteSpace(currentDevice.Type.RevisionNo) &&
                                (uint)EsiUtilities.ParseHexDecString(currentDevice.Type.ProductCode) == productCode &&
                                (uint)EsiUtilities.ParseHexDecString(currentDevice.Type.RevisionNo) == revision;

                    if (found)
                        info = currentInfo;

                    return found;
                });

                if (device != null)
                    break;
            }

            // try to find old revision
            if (device == null)
            {
                etherCATInfos.ToList().ForEach(currentInfo =>
                {
                    device = currentInfo.Descriptions.Devices.Where(currentDevice =>
                    {
                        var found = !string.IsNullOrWhiteSpace(currentDevice.Type.ProductCode) &&
                                    !string.IsNullOrWhiteSpace(currentDevice.Type.RevisionNo) &&
                                    (int)EsiUtilities.ParseHexDecString(currentDevice.Type.ProductCode) == productCode;

                        if (found)
                            info = currentInfo;

                        return found;
                    }).OrderBy(currentDevice => currentDevice.Type.RevisionNo).LastOrDefault();
                });

                // return without success
                if (device == null)
                    return (null, null, null);
            }

            // find group
            if (info == null)
                throw new Exception($"ESI entry for group type '{device}' not found.");

            var group = info.Descriptions.Groups.FirstOrDefault(currentGroup => currentGroup.Type == device.GroupType);

            if (group == null)
                throw new Exception($"ESI entry for group type '{device}' not found.");

            return (info, device, group);
        }

        public static (EtherCATInfoDescriptionsDevice device, EtherCATInfoDescriptionsGroup group) FindEsi(uint manufacturer, uint productCode, uint revision)
        {
            (var etherCATInfo, var device, var group) = EsiUtilities.TryFindDevice(EsiUtilities.CacheEtherCatInfos, manufacturer, productCode, revision);
            return (device, group);
        }

        private static EtherCATInfo loadEsi(string esiFileName)
        {
            EtherCATInfo etherCatInfo;
            var xmlSerializer = new XmlSerializer(typeof(EtherCATInfo));

            using (StreamReader streamReader = new StreamReader(esiFileName))
            {
                try
                {
                    etherCatInfo = (EtherCATInfo)xmlSerializer.Deserialize(streamReader);
                }
                catch (Exception ex)
                {
                    throw new Exception($"Could not open file {esiFileName}. Reason: {ex.Message}");
                }
            }

            etherCatInfo.Descriptions.Devices.ToList().ForEach(y => y.RxPdo = y.RxPdo ?? new PdoType[] { });
            etherCatInfo.Descriptions.Devices.ToList().ForEach(y => y.RxPdo.ToList().ForEach(z => z.Entry = z.Entry ?? new PdoTypeEntry[] { }));

            etherCatInfo.Descriptions.Devices.ToList().ForEach(y => y.TxPdo = y.TxPdo ?? new PdoType[] { });
            etherCatInfo.Descriptions.Devices.ToList().ForEach(y => y.TxPdo.ToList().ForEach(z => z.Entry = z.Entry ?? new PdoTypeEntry[] { }));

            return etherCatInfo;
        }

        public static EtherCATInfo LoadEsi(string esiFileName)
        {
            var info = loadEsi(esiFileName);
            if (info != null)
            {
                CacheEtherCatInfos.Add(info);
            }
            return info;
        }
    }
}
